#include <stdlib.h>
#include <string>
#include <exception>
#include <iostream>
#include <stdio.h>

#include "rep.hpp"

using namespace std;

namespace rep{

  FrameAllocator* FrameAllocator::fa = NULL;


  const uint32_t FrameAllocator::sizetable[] = { 0x0001, 0x0002, 0x0004, 0x0008, 
                                                 0x0010, 0x0020, 0x0040, 0x0080,
                                                 0x0100, 0x0200, 0x0400, 0x0800,
                                                 0x1000, 0x2000, 0x4000, 0x8000};
  
  void FrameAllocator::init(unsigned bits){
    if(fa == NULL){
      fa = new FrameAllocator(bits);
    }
  }

  void FrameAllocator::done(){
    delete fa;
    fa = NULL;
  }

  FrameAllocator::FrameAllocator(unsigned bits) {
    max_bits_per_alloc = DEFAULT_MAX_MEM_INC_BITS;
    bits_per_alloc = bits;
    total_alloc = 0;
    total_avail = 0;
    if(bits <= 0){
      //this is an error condition
      cerr << "FrameAllocator constructed with 0 or fewer bits"<< endl;
      exit(1);
    }
    for(unsigned i=0; i<NUMLISTS; i++){
      freelists[i] = NULL;
    }
    size_t sz = 0x01;
    sz = sz<< bits;
    try{
      alloc_more();
    }catch(bad_alloc e){
      cerr << "Could not allocate initial heap"<<endl;
      exit(1);
    }
  }

  FrameAllocator::~FrameAllocator(){
    //free all the blocks we've allocated
    while(!allocated_blocks.empty()){
      pair<Slot*, unsigned>* killme = allocated_blocks.front();
      allocated_blocks.pop_front();
      delete[] killme->first;
      delete killme;
    }
  }

  /**
   * Allocate more object memory, growing it exponentially until we
   * reach our maximum block size.
   *
   * @modifies this 
   *
   * @effects we end up with around twice the memory we
   * previously had, or 2^max_bits_per_alloc more, whichever is less.
   */
  void FrameAllocator::alloc_more(){
    unsigned bits = bits_per_alloc++;
    uint32_t sz = sizetable[bits];
    Slot* more = new Slot[sz];
    //store the allocated pointer so we can free it later, 
    pair<Slot*, unsigned>* record = new pair<Slot*, unsigned>(more, sz);
    push(bits, more);
    total_avail += sz;
    allocated_blocks.push_back(record);
    if(bits_per_alloc > max_bits_per_alloc) bits_per_alloc = max_bits_per_alloc;
  }

  /**
   * Starting at the logrithmic size bits, returns the index of the
   * next size up that has memory allocated to it.
   *
   * If there is no such size, returns -1.
   */
  int FrameAllocator::next_present(unsigned bits){
    for(unsigned i = bits; i<NUMLISTS; i++){
      if(freelists[i]) return i;
    }
    return -1;
  }

  /**
   * Push the freed object onto the corresponding listnum
   */

  void FrameAllocator::push(unsigned listnum, Slot* freed){
    freed->obj = freelists[listnum];
    freelists[listnum] = freed;
  }

  /**
   * Pop the first slot off the corrsponding list and return it.
   */
  Slot* FrameAllocator::pop(unsigned listnum){
    Slot* ret = freelists[listnum];
    if(ret){
      freelists[listnum] = ret->obj;
    }
    return ret;
  }

  /**
   * Attempts to refill the given list with currently-allocated
   * memory.  Returns 0 on success, < 0 on failure.
   */
  int FrameAllocator::refill(unsigned listnum){
    if(listnum>=NUMLISTS-1){
      return -1;
    }
    //cout<<"refilling "<<listnum<<endl;
    if(freelists[listnum+1] != NULL ||
       refill(listnum+1) == 0){
      //split the list
      unsigned sz = sizetable[listnum];
      Slot* big = pop(listnum+1);
      //cout<<" big is at "<<big<<endl;
      push(listnum, big+sz);
      push(listnum, big);
      return 0;
    }else{
      return -1;
    }
  }

  /**
   * Returns a frame/object of the given size, or NULL if we need to
   * allocate more memory or garbage collect or something.
   */

  Slot* FrameAllocator::getFrameWeak(unsigned size){
    if(!fa){
      cerr<<"Should have already initialized"<<endl;
      return NULL;
    }
    unsigned list;
    //a search loop.
    for(list=0; sizetable[list] < size; list++); 
    if(fa->freelists[list]){
      Slot* ret = fa->pop(list);
      return ret;
    }else if(fa->refill(list) == 0){
      return fa->pop(list);
    }else{
      return NULL;
    }
  }

  /**
   * Does its best to return a nice free frame/object of the given
   * size.  If it fails, we can't continue. (note: this is liable to
   * change once I have a proper story about exception handling in the
   * language, if exception handling can run on some reserve memory or
   * something).
   */

  Slot* FrameAllocator::getFrame(unsigned size){
    Slot* ret = getFrameWeak(size);
    if(ret!=NULL) return ret;
    try{
      fa->alloc_more();
      return getFrame(size);
    }catch(bad_alloc err){
      cerr<<"Unable to allocate more memory"<<endl;
      exit(1);
    }

  }
  
}
